package com.cskaoyan.javase.oop3._3lambda._0introduction;

import java.util.IdentityHashMap;

/**
 * Lambda表达式的引入
 * Lambda 表达式是 JDK8 的一个新特性
 * 可以取代接口的匿名内部类，写出更优雅的Java 代码。
 *
 * 如果说匿名内部类实际上是局部内部类的更进一步，简化了局部内部类
 * 那么Lambda就是匿名内部类更进一步，语法上更简洁了，代码更优雅了，是高端的玩法，是人上人的玩法。
 *
 * 以上描述总体就说了三点：
 *      1.Lambda表达式仍然是局部内部类
 *          是特殊的局部内部类，仍然定义在局部位置。而且局部内部类的注意事项，也一样存在。
 *
 *      2.Lambda表达式在取代匿名内部类时，不是全部都取代
 *          而是取代接口的匿名内部类，而类的匿名内部类Lambda表达式是不能取代的。
 *          (类普遍来说,也不会用匿名内部类创建子类对象)
 *
 *      3.Lambda表达式是匿名内部类的更进一步
 *          所以Lambda表达式得到的也不是一个类，而是一个对象，并且是接口的子类对象。
 *
 * 在具体讲解Lambda表达式的语法之前,我们需要再学习一个前置知识点: 功能接口(Functional Interface)
 * Lambda表达式表示创建接口的子类对象,并不是所有的接口都能创建
 * 它要求接口: 有且仅有一个强制子类实现的抽象方法
 * 在Java中,这种接口,被称之为"功能接口"
 * 用注解"@FunctionalInterface"标记一个接口,如果该接口是功能接口,则不会编译报错
 *
 * 思考以下问题:
 *      1.功能接口是只有一个方法的接口吗?
 *          很明显不是
 *          因为Java8以后的接口,允许定义默认方法和静态方法,它们都不需要子类去重写实现.
 *
 *      2.功能接口中是不是只有一个抽象方法呢?
 *          也不是,功能接口中可以有多个抽象方法
 *      这时我们要对之前,我们讲解的抽象方法相关知识做一个补充了.抽象方法虽然起着占位符的作用
 *          但是也不是所有抽象方法都会强制子类实现(普通类做子类时)
 *          这和Java中所有类的父类Object有关
 *          因为Java中所有类都会继承Object类的成员方法
 *          那么如果把Object类成员方法所对应的抽象方法写在接口中
 *          普通类实现接口,并不需要强制实现这些抽象方法,因为可以用Object类中默认实现作为实现.
 *
 *      (重要)Object类当中的成员方法所对应的抽象方法应该长啥样呢?(方法的重写语法的反推)
 *          返回值类型必须保持完全一致(当然如果是引用数据类型,需要反推兼容)
 *          方法名必须保持完全一致
 *          形参列表也必须保持完全一致,但是形参的名字无所谓
 *
 * 实际上功能接口,在绝大多数情况下,还是以单个接口,单个抽象方法为主,但是也应该知道特殊情况.
 *
 * 功能接口具备后,先来看一下Lambda表达式的语法:
 * (形参列表) -> {
 * // 方法体
 * }
 * 解释:
 *      1."(形参列表)"是需要直接照抄功能接口中那个强制子类实现的抽象方法的形参列表
 *      2."->" 这是由一个"-"和">"组成,像一个箭头,它读叫"goes to",它是Lambda表达式的运算符
 *      3."{}"是功能接口中强制子类实现的抽象方法的重写方法体,它是一个方法体
 *          所以Lambda表达式表示的子类对象,是不能新增自身独有成员的
 *
 * 为什么Lambda表达式要求,接口必须是功能接口呢?
 *      Lambda表达式语法结构当中就只有一套方法重写的结构,只能重写唯一的一个方法
 *
 * 如果按照以上语法,直接写Lambda表达式,会编译报错
 * 这是因为Lambda表达式的语法是不能直接判断Lambda表达式所创建对象的类型的
 *
 * 这时要想通过编译,就需要使得编译器知道Lambda表达式所创建的子类对象究竟是哪个功能接口的子类对象,这个过程在Java中称之为"Lambda表达式的类型推断"
 *
 * 完成类型推断有以下几种方式:
 *      1.直接用功能接口的引用接收Lambda表达式,完成类型推断
 *
 *      2.(了解)Lambda表达式类型推断的特殊语法,类似于强制类型转换
 *      直接告诉编译器Lambda表达式创建对象的类型
 *
 *      3.(重点)借助方法来完成类型推断
 *          a.借助方法的形参数据类型完成类型推断
 *              需要形参的数据类型是一个功能接口
 *          b.借助方法的返回值类型完成类型推断
 *              需要方法的返回值数据类型是一个功能接口
 *
 * 注意: 匿名内部类对象在使用父类引用接收匿名子类对象时,会存在无法访问匿名子类独有成员的问题
 * 但是Lambda表达式不存在这个问题,因为语法结构中"{}"是重写的方法体,Lambda表达式表示的子类对象,没有办法新增自身独有成员.
 *
 * 以上Lambda表达式的基本使用就完了!
 *
 * @since 16:33
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        // 用Lambda表达式创建接口IDemo的子类对象并直接用功能接口的引用接收,完成类型推断
        IDemo id = () -> {
            System.out.println("66666");
        };
        id.test();
        // 类似于强制类型转换的Lambda表达式类型推断的特殊语法
        ((IDemo) (() -> {
            System.out.println("777777");
        })).test();

        method(() -> {
            System.out.println("我喜欢吃冰淇淋!");
        });
        IDemo id2 = method();
        id2.test();
    }

    public static void method(IDemo id) {
        id.test();
    }

    public static IDemo method() {
        return () -> {
            System.out.println("我喜欢吃西瓜!");
        };
    }
}

@FunctionalInterface
interface IDemo {
    void test();
}

@FunctionalInterface
interface IC {
    void test();

    // 写Object类当中hashCode方法对应的抽象方法
    int hashCode();

    // 写Object类当中equals方法对应的抽象方法
    boolean equals(Object a);

    default void testDefault() {
    }

    static void testStatic() {
    }
}

class ICImpl implements IC {
    @Override
    public void test() {
        System.out.println();
    }
}

// @FunctionalInterface 不是功能接口
interface IA {
}

// @FunctionalInterface 不是功能接口
interface IB {
    void test();

    void test2();
}